package com.yuncheng.sms.service;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.yuncheng.common.api.BeforeLoginAPI;
import com.yuncheng.common.utils.CommonUtils;
import com.yuncheng.constant.CacheConstant;
import com.yuncheng.constant.CommonConstant;
import com.yuncheng.sms.utils.LoginSysConfig;
import com.yuncheng.sms.utils.SmsClient;
import com.yuncheng.sms.utils.SmsHelper;
import com.yuncheng.system.api.SystemAPI;
import com.yuncheng.system.orguser.entity.SysUser;
import com.yuncheng.system.orguser.service.ISysUserService;
import com.yuncheng.system.sysconfig.entity.SysConfig;
import com.yuncheng.system.sysconfig.service.ISysConfigService;
import com.yuncheng.system.userlock.service.SysUserLockService;
import com.yuncheng.system.userlock.service.UserLoader;
import com.yuncheng.utils.RedisUtil;
import com.yuncheng.utils.SpringContextUtils;
import com.yuncheng.vo.HttpResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * LoginSysConfigService
 *
 * @date 2023/4/10
 */
@Service
public class LoginSysConfigService implements InitializingBean {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    public static final String LOGIN_USERNAME = "username";
    public static final String LOGIN_PASSWORD = "password";

    public static final String LOGIN_ISLAN_KEY = "islan";
    public static final String LOGIN_ISLAN_NOVERIFY = "1";

    public static final int NO_SMSCODE = 0;
    public static final int NEED_SMSCODE = 1;

    public static final String SID_RANDON_CHR_POOL = "ABCDEFGHJKMNPRSTUVWXYZ";

    public static final String TEMPLATE_PREFIX = "${";
    public static final String TEMPLATE_SUFFIX = "}";

    public static final Pattern PHONE_PATTERN = Pattern.compile("^1[0-9]{10}$");

    public static final String SMS_SEND_ID_KEY = "sendid";
    public static final String SMS_SEND_VERIFYCODE_KEY = "verifycode";
    public static final String SMS_SEND_MSG_KEY = "msg";
    public static final String SMS_SEND_ERROR_MSG = "用户没有设置手机号或者未正确设置手机号，无法发送手机验证码！";

    public static final String SMS_VERIFYCODE_CACHE_PREFIX = "login:smscode:";

    public static final String SMS_SIGN_CACHE_PREFIX = "login:smscode:sign:";

    @Value("${login_smscode_expired_minutes:10}")
    private Integer smscodeExpiredMinutes;

    @Autowired
    private ISysConfigService sysConfigService;

    @Autowired
    private ISysUserService sysUserService;

    @Autowired
    private SysUserLockService sysUserLockService;

    @Autowired
    private UserLoader userLoader;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private SystemAPI systemAPI;

    @Resource
    private SmsClient smsClient;

    public int checkPcNeedSmsCode(String ip) {
        /** 20240624 注释 */
        int smscode = NO_SMSCODE;
//        int smscode = NEED_SMSCODE;
        if (!SmsHelper.isInnerNetworkIp(ip)) {
            //非内网IP
            String needSmsCode = sysConfigService.querySysConfigByCode(LoginSysConfig.LOGIN_PC_VERIFY_SMSCODE.getConfigCode());
            if (StringUtils.hasText(needSmsCode) && "1".equals(needSmsCode)) {
                smscode = NEED_SMSCODE;
            }
        }
        return smscode;
    }

    public int checkMobileNeedSmsCode(String ip) {
        int smscode = NO_SMSCODE;
        if (!SmsHelper.isInnerNetworkIp(ip)) {
            //非内网IP
            String needSmsCode = sysConfigService.querySysConfigByCode(LoginSysConfig.LOGIN_WLAN_VERIFY_SMSCODE.getConfigCode());
            if (StringUtils.hasText(needSmsCode) && "1".equals(needSmsCode)) {
                smscode = NEED_SMSCODE;
            }
        }
        return smscode;
    }

    public String buildSignCodeKey(String username, String sendid) {
        return SMS_SIGN_CACHE_PREFIX + username + ":" + sendid;
    }

    public void saveSignCode(String username, String sendid) {
        String key = buildSignCodeKey( username, sendid);
        redisUtil.set(key, "1");
        redisUtil.expire(key, this.smscodeExpiredMinutes * 60);
    }

    public boolean hasSignCode(String username, String sendid) {
        String key = buildSignCodeKey( username, sendid);
        return redisUtil.hasKey(key);
    }

    public void removeSignCode(String username, String sendid) {
        String key = buildSignCodeKey( username, sendid);
        redisUtil.del(key);
    }

    public void saveVerifyCode(String username, String sendid, String verifycode) {
        String key = SMS_VERIFYCODE_CACHE_PREFIX + username + ":" + sendid;
        redisUtil.set(key, verifycode);
        redisUtil.expire(key, this.smscodeExpiredMinutes * 60);
    }

    public String loadVerifyCode(String username, String sendid) {
        String key = SMS_VERIFYCODE_CACHE_PREFIX + username + ":" + sendid;
        return (String) redisUtil.get(key);
    }

    public void removeVerifyCode(String username, String sendid) {
        String key = SMS_VERIFYCODE_CACHE_PREFIX + username + ":" + sendid;
        redisUtil.del(key);
    }

    public String obtainUserName(HttpServletRequest request) {
        return request.getParameter(LOGIN_USERNAME);
    }

    public String obtainSendId(HttpServletRequest request) {
        return request.getParameter(SMS_SEND_ID_KEY);
    }

    public String obtainVerifyCode(HttpServletRequest request) {
        return request.getParameter(SMS_SEND_VERIFYCODE_KEY);
    }

    public static final Pattern MD5_STR_PATTERN = Pattern.compile("^[a-f0-9]{32}$");

    private boolean isMd5String(String s) {
        if (null != s) {
            Matcher m = MD5_STR_PATTERN.matcher(s);
            return m.matches();
        }
        return false;
    }

    public boolean matchPassword(SysUser sysUser, String password) {
        boolean isMD5Password = isMd5String(sysUser.getPassword());
        String userpassword = isMD5Password ? CommonUtils.md5DigestAsHex(password) : CommonUtils.md5Crypt(password, sysUser.getSalt());
        String syspassword = sysUser.getPassword();
        return syspassword.equals(userpassword);
    }

    private void checkBeforeLogin(SysUser sysUser) {
        Map<String, BeforeLoginAPI> beforeLoginMap = SpringContextUtils.getApplicationContext().getBeansOfType(BeforeLoginAPI.class);
        for (Map.Entry<String, BeforeLoginAPI> entry : beforeLoginMap.entrySet()) {
            try {
                String r = entry.getValue().execute(sysUser);
                if (r != null) {
                    throw new RuntimeException(r);
                }
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        }
    }

    public boolean checkUserData(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter(LOGIN_USERNAME);
        String password = request.getParameter(LOGIN_PASSWORD);

        if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
            SysUser ud = userLoader.loadUserByUsername(username);
            if (null != ud) {
                checkBeforeLogin(ud);
                if (matchPassword(ud, password)) {
                    return true;
                } else {
                    sysUserLockService.passwordError(ud);
                }
            }
        }

        return false;
    }

    public String buildSmsWithTemplate(Map<String, String> dataMap, String smsTemplate) {
        String result = smsTemplate;
        for (Map.Entry<String, String> me : dataMap.entrySet()) {
            String key = TEMPLATE_PREFIX + me.getKey() + TEMPLATE_SUFFIX;
            String data = me.getValue();
            result = StringUtils.replace(result, key, data);
        }
        return result;
    }

    public boolean sendSmsVerifyCodeWithTemplate(String username, String phone, String sendid, String verifycode, String smsTemplate) {
        Map<String, String> dataMap = new HashMap<>();
        dataMap.put(SMS_SEND_ID_KEY, sendid);
        dataMap.put(SMS_SEND_VERIFYCODE_KEY, verifycode);

        String smsContent = buildSmsWithTemplate(dataMap, smsTemplate);

        if (null == smsClient) {
            logger.info(username + " 发送验证码短信失败！返回的信息为：未配置短信发送接口！");
            return false;
        }

        HttpResult<?> result = smsClient.sendMessage(phone, smsContent, "", "");

        if (result.isSuccess()) {
            logger.info(username + " 发送验证码短信：" + smsContent);
            if ("1".equals(this.sysConfigService.querySysConfigByCode(LoginSysConfig.LOGIN_SMSCODE_SAVE_SYSLOG.getConfigCode()))) {
                systemAPI.addLog(username, "发送验证码短信：" + smsContent, CommonConstant.LOG_TYPE_OPERATE, CommonConstant.OPERATE_TYPE_ADD);
            }
            return true;
        } else {
            logger.info(username + " 发送验证码短信失败！返回的信息为：" + result.getMessage());
            return false;
        }
    }

    public String getUserPhone(String username) {
        String result = null;
        SysUser osu = userLoader.loadUserByUsername(username);
        if (null != osu) {
            result = osu.getPhone();
            if (null != result) {
                result = result.trim();
                if (!PHONE_PATTERN.matcher(result).matches()) {
                    result = null;
                }
            }
        }
        return result;
    }

    public Map<String, Object> sendSmsVerifyCode(String username) {
        String phone = getUserPhone(username);
        return sendSmsVerifyCode(username,phone);
    }

    public Map<String, Object> sendSmsVerifyCode(String username,String phone) {
        Map<String, Object> result = new HashMap<>();

        if (StringUtils.hasText(username)) {
            String sendid = RandomUtil.randomString(SID_RANDON_CHR_POOL, 1) + RandomUtil.randomNumbers(2);
            String verifycode = RandomUtil.randomNumbers(6);

            if ("1".equals(this.sysConfigService.querySysConfigByCode(LoginSysConfig.LOGIN_BLOCK_SMSCODE.getConfigCode()))) {
                result.put(SMS_SEND_VERIFYCODE_KEY, verifycode);
                result.put(SMS_SEND_MSG_KEY, "已直接获取验证码。");
                result.put(SMS_SEND_ID_KEY, sendid);
                saveVerifyCode(username, sendid, verifycode);
            } else {
                if (null != phone) {
                    if (sendSmsVerifyCodeWithTemplate(username, phone, sendid, verifycode,
                            this.sysConfigService.querySysConfigByCode(LoginSysConfig.LOGIN_SMSCODE_TEMPLATE.getConfigCode()))) {
                        result.put(SMS_SEND_MSG_KEY, "手机验证码已发送。");
                        result.put(SMS_SEND_ID_KEY, sendid);
                        saveVerifyCode(username, sendid, verifycode);
                    } else {
                        result.put(SMS_SEND_MSG_KEY, "手机验证码发送失败，请联系管理员！");
                    }
                } else {
                    result.clear();
                    result.put(SMS_SEND_MSG_KEY, SMS_SEND_ERROR_MSG);
                }
            }
        } else {
            result.clear();
            result.put(SMS_SEND_MSG_KEY, SMS_SEND_ERROR_MSG);
        }

        return result;
    }

    public boolean checkSmsVerifyCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String username = obtainUserName(request);
        String sendid = obtainSendId(request);
        String verifycode = obtainVerifyCode(request);

        logger.info("checkSmsVerifyCode with (" + username + ", " + sendid + ", " + verifycode + ")");

        if (StringUtils.hasText(username) && StringUtils.hasText(sendid) && StringUtils.hasText(verifycode)) {
            String savedVerifyCode = loadVerifyCode(username, sendid);
            logger.info("checkSmsVerifyCode Data Is {}", savedVerifyCode);

            if (verifycode.equals(savedVerifyCode)) {
                removeVerifyCode(username, sendid);
                return true;
            }
        }

        HttpResult resultObj = HttpResult.error(401, "无效的验证码！");

        Map<String, Object> result = new HashMap<>();
        result.put("smscode", 1);
        resultObj.setResult(result);

        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(JSON.toJSONString(resultObj));

        return false;
    }

    //数字
    public static final String REG_NUMBER = ".*\\d+.*";
    //小写字母
    public static final String REG_UPPERCASE = ".*[A-Z]+.*";
    //大写字母
    public static final String REG_LOWERCASE = ".*[a-z]+.*";

    public static final int PASSWORD_MIN_LEN = 8;

    public boolean checkPasswordRule(String password) {
        if (StringUtils.hasText(password) && (password.length() >= PASSWORD_MIN_LEN)) {
            int i = 0;
            if (password.matches(REG_NUMBER)) i++;
            if (password.matches(REG_UPPERCASE)) i++;
            if (password.matches(REG_LOWERCASE)) i++;

            return (3 == i);
        }

        return false;
    }

    private String getSalt(SysUser user) {
        String salt = user.getSalt();
        if (!StringUtils.hasText(salt)) {
            if (isMd5String(user.getPassword())) {
                // 用户原密码是md5格式的，则生成新的salt
                return CommonUtils.getMd5Salt();
            } else if (user.getPassword().startsWith("$2a$10$")) {
                //crypt密码密文的头部29位就是salt，"$2a$10$"+22位组成salt
                return user.getPassword().substring(0, 29);
            }
        } else {
            return salt;
        }
        return null;
    }

    private String encodePassword(SysUser user, String password) {
        if (isMd5String(user.getPassword())) {
            return CommonUtils.md5DigestAsHex(password);
        } else {
            return CommonUtils.md5Crypt(password, getSalt(user));
        }
    }

    public HttpResult<?> changeWeakPassword(String username, String oldpassword, String newpassword, String confirmpassword) {
        SysUser user = userLoader.loadUserByUsername(username);
        if (null != user) {
            String salt = getSalt(user);
            String passwordEncode = encodePassword(user, oldpassword);
            if (!user.getPassword().equals(passwordEncode)) {
                systemAPI.addLog(username, "修改密码时，旧密码输入错误", CommonConstant.LOG_TYPE_OPERATE, CommonConstant.OPERATE_TYPE_ADD);
                return HttpResult.error("旧密码输入错误");
            }
            if (!StringUtils.hasText(newpassword)) {
                systemAPI.addLog(username, "修改密码时，新密码为空", CommonConstant.LOG_TYPE_OPERATE, CommonConstant.OPERATE_TYPE_ADD);
                return HttpResult.error("新密码不允许为空");
            }
            if (!newpassword.equals(confirmpassword)) {
                systemAPI.addLog(username, "修改密码时，两次输入密码不一致", CommonConstant.LOG_TYPE_OPERATE, CommonConstant.OPERATE_TYPE_ADD);
                return HttpResult.error("两次输入密码不一致");
            }
            if (!checkPasswordRule(newpassword)) {
                systemAPI.addLog(username, "修改密码时，新密码不符合安全要求", CommonConstant.LOG_TYPE_OPERATE, CommonConstant.OPERATE_TYPE_ADD);
                return HttpResult.error("新密码不符合安全要求");
            }

            String password = CommonUtils.md5Crypt(newpassword, salt);
            //保存新密码到数据库
            SysUser updatePassword = new SysUser();
            updatePassword.setId(user.getId());
            updatePassword.setSalt(salt);
            updatePassword.setPassword(password);
            if (sysUserService.updateById(updatePassword)) {
                systemAPI.addLog(username, "修改密码为安全密码", CommonConstant.LOG_TYPE_OPERATE, CommonConstant.OPERATE_TYPE_ADD);
            } else {
                systemAPI.addLog(username, "修改密码时出现异常情况", CommonConstant.LOG_TYPE_OPERATE, CommonConstant.OPERATE_TYPE_ADD);
                return HttpResult.error("密码修改失败，请联系系统管理员!");
            }
            return HttpResult.ok("密码修改成功!");
        }

        return HttpResult.error("无效的请求数据");
    }

    @CacheEvict(value = CacheConstant.SYS_CONFIG_CACHE, allEntries = true)
    public void refreshConfig() {
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Stream.of(LoginSysConfig.values()).forEach(config -> {
            SysConfig value = sysConfigService.lambdaQuery().eq(SysConfig::getConfigCode, config.getConfigCode())
                    .list().stream().findFirst().orElse(null);
            if (null == value) {
                SysConfig sc = new SysConfig();
                sc.setConfigCode(config.getConfigCode());
                sc.setConfigValue(config.getConfigDefaultValue());
                sc.setConfigName(config.getConfigName());
                sysConfigService.save(sc);
            } else {
                if (!config.getConfigName().equals(value.getConfigName())) {
                    SysConfig sc = new SysConfig();
                    sc.setConfigName(config.getConfigName());
                    sc.setId(value.getId());
                    sysConfigService.updateById(sc);
                }
            }
        });
    }

    public HttpResult<?> changeUserPhone(String loginName, String phone, String sendid, String verifycode) {
        String code = this.loadVerifyCode(loginName,sendid);
        this.removeVerifyCode(loginName,sendid);
        if(code!=null&&code.equals(verifycode)){
            SysUser user = sysUserService.getUserByName(loginName);
            if(user!=null){
                user.setPhone(phone);
                sysUserService.updateById(user);
                return HttpResult.ok("修改成功");
            }else{
                return HttpResult.error("账号不存在，可尝试重新登录");
            }
        }
        return HttpResult.error("验证码错误，请检查是否输入错误");
    }
}
